/*
 * @Author: jianglijun1004 
 * @Date: 2020-03-10 00:24:36 
 * @Last Modified by: jianglijun
 * @Last Modified time: 2020-03-13 02:31:23
 */
const rp = require('request-promise')
const qs = require('qs')
const fs = require('fs')
const baseURL = 'https://api.weixin.qq.com/cgi-bin/'
const mpBase = 'https://mp.weixin.qq.com/cgi-bin/'
const semanticUrl = 'https://api.weixin.qq.com/semantic/semproxy/search'
const api = {
  semanticUrl, // 发送语义理解请求
  accessToken: baseURL + 'token', // access_token
  // 临时素材
  temporary: {
    upload: baseURL + 'media/upload',
    fetch: baseURL + 'media/get'
  },
  // 永久素材
  permanent: {
    upload: baseURL + 'material/add_material',
    uploadNews: baseURL + 'material/add_news', // 永久图文素材
    uploadNewsPic: baseURL + 'media/uploadimg', // 返回图片URL
    fetch: baseURL + 'material/get_material',
    delete: baseURL + 'material/del_material',
    update: baseURL + 'material/update_news',
    count: baseURL + 'material/get_materialcount',
    batch: baseURL + 'material/batchget_material'
  },
  // 标签
  tag: {
    create: baseURL + 'tags/create',
    fetch: baseURL + 'tags/get',
    update: baseURL + 'tags/update',
    del: baseURL + 'tags/delete',
    fetchUsers: baseURL + 'user/tag/get', // 获取标签下粉丝列表
    batchTag: baseURL + 'tags/members/batchtagging', // 批量为用户打标签
    batchUnTag: baseURL + 'tags/members/batchuntagging', // 批量为用户取消标签
    getUserTags: baseURL + 'tags/getidlist' // 获取用户身上的标签列表
  },
  user: {
    fetch: baseURL + 'user/get', // 获取帐号的关注者列表
    remark: baseURL + 'user/info/updateremark', //设置备注名
    info: baseURL + 'user/info', // 通过OpenID来获取用户基本信息
    batch: baseURL + 'user/info/batchget' // 批量获取用户基本信息
  },
  // 群发
  mass: {
    uploadnews: baseURL + 'media/uploadnews', // 上传图文消息素材
    sendall: baseURL + 'message/mass/sendall', // 根据标签进行群发
    uploadVideo: baseURL + 'media/uploadvideo', // 获取vido media_id
    sendOpendID: baseURL + 'message/mass/send', // 根据OpenID列表群发
    deleSend: baseURL + 'message/mass/delete', // 删除群发
    preview: baseURL + 'message/mass/preview', // 预览接口
    get: baseURL + 'message/mass/get', // 查询群发消息发送状态
  },
  // 二维码
  qrcode: {
    create: baseURL + 'qrcode/create',
    show: mpBase + 'showqrcode'
  },
  // 长链接转成短链接
  shortUrl: {
    create: baseURL + 'shorturl'
  },
  ai: {
    addvoicet: baseURL + 'media/voice/addvoicetorecofortext', // 提交语音
    queryrecoresult: baseURL + 'media/voice/queryrecoresultfortext', // 获取语音识别结果
    translate: baseURL + 'media/voice/translatecontent' //微信翻译
  },
  menu: {
    create: baseURL + 'menu/create', // 创建普通菜单
    del: baseURL + 'menu/delete', //删除菜单
    query: baseURL + 'get_current_selfmenu_info',
    custom: baseURL + 'menu/addconditional',
    delCustom: baseURL + 'menu/delconditional',
    trymatch: baseURL + 'menu/trymatch',
    fetch: baseURL + 'menu/get'
  },
  ticket: {
    get: baseURL + 'ticket/getticket'
  }
}
module.exports = class Wechat {
  constructor(option) {
    this.option = Object.assign({}, option)
    this.appID = option.appID
    this.appSecret = option.appSecret
    this.getAccessToken = option.getAccessToken
    this.saveAccessToken = option.saveAccessToken
    this.getTicket = option.getTicket
    this.saveTicket = option.saveTicket
    this.fetchAccessToken()
  }
  // 封装请求
  async request(opts) {
    opts = Object.assign({}, opts, {
      json: true
    })
    try {
      const data = await rp(opts);
      return data;
    } catch (error) {
      console.log(error)
    }
  }
  // 1从数据获取acesstoken 2.判断是否过去过期 过期更新3.返回acesseToken
  async fetchAccessToken() {
    let data = await this.getAccessToken();
    if (!this.isValideToken(data, 'access_token')) {
      data = await this.updateAccessToken();
      await this.saveAccessToken(data);
    }
    return data;
  }
  // 更新accesstoken
  async updateAccessToken() {
    const url = api.accessToken + qs.stringify({
      grant_type: 'client_credential',
      appid: this.appID,
      secret: this.appSecret
    }, {
      addQueryPrefix: true
    })
    const data = await this.request({
      url
    });
    if (data['access_token']) {
      const now = new Date().valueOf();
      const expires_in = now + (data.expires_in - 25) * 1000;
      data.expires_in = expires_in;
    }
    return data;
  }
  // 从数据库获取ticker 2 判断3 返回ticker
  async fetchTicket(token) {
    let data = await this.getTicket();
    if (!this.isValideToken(data, 'ticket')) {
      data = await this.updateTicket(token);
      await this.saveTicket(data);
    }
    return data;
  }
  async updateTicket(token) {
    const url = `${api.ticket.get}?access_token=${token}&type=jsapi`

    const data = await this.request({
      url
    })
    if (!data.errcode) {
      const now = new Date().valueOf();
      const expires_in = now + (data.expires_in - 20) * 1000
      data.expires_in = expires_in
    } else {
      console.log(data)
    }
    return data
  }
  // 验证accessToken 是否获取
  isValideToken(data, name) {
    if (!data || !data[name]) {
      return false;
    }
    const expires_in = +data.expires_in;
    const now = +new Date().valueOf();
    if (now < expires_in) {
      return true;
    } else {
      return false;
    }
  }
  // 上传素材
  uploadMaterial(token, type, material, permanent = false) {
    let form = {}
    let url = api.temporary.upload
    // 永久素材 form 是个 obj，继承外面传入的新对象
    if (permanent) {
      url = api.permanent.upload
      form = Object.assign(form, permanent)
    }
    // 上传图文消息的图片素材
    if (type === 'pic') {
      url = api.permanent.uploadNewsPic // 
    }
    // 图文非图文的素材提交表单的切换
    if (type === 'news') {
      url = api.permanent.uploadNews // 图文 永久 图文 body数组
      form = material
    } else {
      form.media = fs.createReadStream(material)
    }
    let uploadUrl = url + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })

    // 根据素材永久性填充 token
    if (!permanent) {
      uploadUrl += `&type=${type}`
    } else {
      if (type !== 'news') {
        form.access_token = token
        if (type !== 'pic') {
          uploadUrl += `&type=${type}`
        }
      }
    }
    const options = {
      method: 'POST',
      url: uploadUrl,
    }
    // 图文和非图文在 request 提交主体判断
    if (type === 'news') {
      options.body = form
    } else {
      options.formData = form
    }
    return options
  }

  // 获取素材
  fetchMaterial(token, mediaId, type, permanent) {
    let form = {}
    let fetchUrl = api.temporary.fetch

    if (permanent) {
      fetchUrl = api.permanent.fetch
    }
    let url = fetchUrl + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })
    let options = {};
    if (permanent) {
      form.media_id = mediaId
      form.access_token = token
      options.body = form
      options.method = 'POST'
    } else {
      if (type === 'video') {
        url = url.replace('https:', 'http:')
      }
      options.method = 'GET';
      url += '&media_id=' + mediaId
    }
    options.url = url;
    return options
  }
  // 删除素材
  deleteMaterial(token, media_id) {
    const url = api.permanent.delete + qs.stringify({
      access_token: token,
      media_id
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url,
      body: {
        media_id
      }
    }
  }
  // 更新素材
  updateMaterial(token, media_id, news) {
    let form = {
      media_id
    }
    form = Object.assign(form, news)
    const url = api.permanent.update + qs.stringify({
      access_token: token,
      media_id
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url,
      body: form
    }
  }

  // 获取素材总数
  countMaterial(token) {
    const url = api.permanent.count + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })
    // const url = `${api.permanent.count}access_token=${token}`
    return {
      method: 'POST',
      url
    }
  }

  // 获取素材列表
  batchMaterial(token, options) {
    options.type = options.type || 'image'
    options.offset = options.offset || 0
    options.count = options.count || 10
    const url = `${api.permanent.batch}?access_token=${token}`
    return {
      method: 'POST',
      url,
      body: options
    }
  }
  // 创建标签
  createTag(token, name) {
    const body = {
      tag: {
        name
      }
    }
    const url = api.tag.create + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url,
      body
    }
  }

  // 获取全部的标签
  fetchTags(token) {
    const url = api.tag.fetch + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })
    return {
      url
    }
  }
  // 编辑标签
  updateTag(token, id, name) {
    const body = {
      tag: {
        id,
        name
      }
    }
    const url = api.tag.update + qs.stringify({
      access_token: token
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 删除标签
  delTag(token, id) {
    const body = {
      tag: {
        id
      }
    }
    const url = api.tag.del + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 获取标签下的粉丝列表
  fetchTagUsers(token, id, openId) {
    const body = {
      tagid: id,
      next_openid: openId || ''
    }
    const url = api.tag.fetchUsers + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 批量加标签和取消标签 unTag=false 打标签
  batchTag(token, openidList, id, unTag) {
    const body = {
      openid_list: openidList,
      tagid: id
    }
    let url = !unTag ? api.tag.batchTag : api.tag.batchUnTag
    url += '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 获取用户身上的标签列表
  getUserTags(token, openid) {
    const body = {
      openid
    }
    const url = api.tag.getUserTags + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 给用户设置别名 服务号专用接口
  remarkUser(token, openId, remark) {
    const body = {
      openid: openId,
      remark
    }
    const url = api.user.remark + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 给用户设置别名 服务号专用接口
  remarkUser(token, openid, remark) {
    const body = {
      openid,
      remark
    }
    const url = api.user.remark + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 获取粉丝列表
  fetchUserList(token, openId) {
    const url = api.user.fetch + '?access_token=' + token + '&next_openid=' + (openId || '')
    return {
      url
    }
  }
  // 获取用户的详细信息
  getUserInfo(token, openId, lang = 'zh_CN') {
    const url = api.user.info + '?access_token=' + token + '&openid=' + openId + '&lang=' + lang
    return {
      url
    }
  }
  // 批量获取用户详细信息
  fetchBatchUsers(token, openIdList) {
    const body = {
      user_list: openIdList
    }
    const url = api.user.batch + '?access_token=' + token
    return {
      method: 'POST',
      url,
      body
    }
  }

  // 上传图文消息素材 群发
  /**
   * @description
   * @author jianglijun1004
   * @date 2020-03-10
   * @param {*} token
   * @param {*} options thumb_media_id 暂时只能用临时thumb的 media_id
   * @returns
   */
  massUploadNews(token, ...options) {
    // thumb_media_id 暂时只能用临时thumb的 media_id
    const url = `${api.mass.uploadnews}?access_token=${token}`;
    const body = {
      articles: options
    }
    return {
      method: 'POST',
      url,
      body
    }
  }

  // 根据标签进行群发
  /**
   * @description 根据标签进行群发
   * @author jianglijun1004
   * @date 2020-03-10
   * @param {*} type 类型 图文消息---news 文本---text 语音--voice
   * 图片-- - image 视频----video 
   * @param {*} content text 直接给文本 其他的给media_id  video比较特殊
   * @param {*} groupId
   * @returns
   */
  sendByGroup(token, type, content, groupId) {
    const url = api.mass.sendall + '?access_token=' + token;
    let msg = {
      filter: {},
      msgtype: type
    }
    if (type === 'news' || type === 'video') {
      const vtype = 'mp' + type;
      msg['msgtype'] = vtype;
      msg[vtype] = {
        'media_id': content
      };
    } else {
      if (type === 'text') {
        msg['text'] = {
          content
        };
      } else {
        msg[type] = {
          'media_id': content
        }
      }
    }
    if (type === 'news') msg['send_ignore_reprint'] = 1
    if (!groupId) {
      msg.filter.is_to_all = true;
    } else {
      msg.filter.is_to_all = false;
      msg.filter.tag_id = groupId;
    }
    return {
      body: msg,
      url,
      method: 'post'
    }
  }
  // 根据标签群发视频 要根据永久视频的media_id 换取新的media_id
  /**
   * @description
   * @author jianglijun1004
   * @date 2020-03-10
   * @param {*} token
   * @param {*} options {media_id,title,description}
   */
  sendByMedia(token, options) {
    options.title = options.title || '';
    options.description = options.description || '';
    const url = api.mass.uploadVideo + '?access_token=' + token
    return {
      method: 'POST',
      body: options,
      url
    }
  }
  // 根据OpenID列表群发
  sendByOpenID(token, type, content, openidList) {
    const url = api.mass.sendOpendID + '?access_token=' + token;
    let msg = {
      touser: openidList,
      msgtype: type
    }
    if (type === 'news') msg['send_ignore_reprint'] = 1;
    if (type === 'news' || type === 'video') {
      const vtype = 'mp' + type;
      msg['msgtype'] = vtype;
      msg[vtype] = {
        'media_id': content
      };
    } else {
      if (type === 'text') {
        msg['text'] = {
          content
        };
      } else {
        msg[type] = {
          'media_id': content
        }
      }
    }
    return {
      method: 'POST',
      body: msg,
      url
    }
  }
  // 删除群发options参数是对象 能删除图文消息和视频消息
  deleteSend(token, options) {
    const url = api.mass.deleSend + '?access_token=' + token;
    return {
      method: "POST",
      url,
      body: options
    }
  }
  // 预览接口
  sendPreview(token, type, content, openId) {
    let msg = {
      touser: openId,
      msgtype: type
    }
    if (type === 'news' || type === 'video') {
      const vtype = 'mp' + type;
      msg['msgtype'] = vtype;
      msg[vtype] = {
        'media_id': content
      };
    } else {
      if (type === 'text') {
        msg['text'] = {
          content
        };
      } else {
        msg[type] = {
          'media_id': content
        }
      }
    }
    return {
      method: 'POST',
      body: msg,
      url: api.mass.preview + '?access_token=' + token
    }
  }
  // 查询群发消息发送状态
  sendGet(token, msg_id) {
    return {
      method: "POST",
      body: {
        msg_id
      },
      url: api.mass.get + '?access_token=' + token
    }
  }
  // 创建二维码 Ticket
  /**
   * @description
   * @author jianglijun1004
   * @date 2020-03-10
   * @param {*} token
   * @param {*} qr {time,scene}
   * time 不传为永久 scene 字符串-- 或数字
   * @returns
   */
  createQrcode(token, qr) {
    const {
      time,
      scene
    } = qr;
    let obj = {
      action_name: '',
      action_info: {
        scene: {}
      }
    };
    if (time) {
      obj['expire_seconds'] = time
    }
    if (Number.isInteger(scene)) {
      if (!time) {
        obj.action_name = 'QR_LIMIT_SCENE'
      } else {
        obj.action_name = "QR_SCENE";
      }
      obj['action_info']['scene']['scene_id'] = scene
    } else {
      if (!time) {
        obj.action_name = 'QR_LIMIT_STR_SCENE'
      } else {
        obj.action_name = "QR_STR_SCENE";
      }
      obj['action_info']['scene']['scene_str'] = scene
    }
    const url = api.qrcode.create + '?access_token=' + token;
    return {
      method: 'POST',
      url,
      body: obj
    }
  }
  // 通过ticket换取二维码
  showQrcode(ticket) {
    const url = api.qrcode.show + '?ticket=' + encodeURI(ticket)
    return url
  }
  // 长链接转短链接
  createShortUrl(token, longurl, action = 'long2short') {
    const url = api.shortUrl.create + '?access_token=' + token
    const body = {
      action,
      long_url: longurl
    }
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 语义理解-查询特定的语句进行分析
  semantic(token, semanticData) {
    const url = api.semanticUrl + '?access_token=' + token
    semanticData.appid = this.appID
    return {
      method: 'POST',
      url,
      body: semanticData
    }
  }
  // 提交语音
  addvoice(token, media, option) {
    let form = {
      media: fs.createReadStream(media)
    }
    // option.access_token = token
    option.format = option.format || 'mp3';
    option.voice_id = option.voice_id || 1011;
    option.lang = option.lang || 'zh_CN';
    const url = api.ai.addvoicet + qs.stringify({
      access_token: token,
      ...option
    }, {
      addQueryPrefix: true
    })
    const options = {
      method: "POST",
      url,
      formData: form
    }
    return options
  }
  // 获取语音识别结果
  queryrecoresult(token, voice_id, lang = 'zh_CN') {
    const url = api.ai.queryrecoresult + qs.stringify({
      access_token: token,
      voice_id,
      lang
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url
    }
  }
  // 微信翻译
  aiTranslate(token, body, lfrom, lto) {
    const url = api.ai.translate + qs.stringify({
      access_token: token,
      lfrom,
      lto
    }, {
      addQueryPrefix: true
    })
    return {
      method: 'POST',
      url,
      body
    }
  }
  // 封装用来请求接口的入口方法
  async handle(operation, ...args) {
    const tokenData = await this.fetchAccessToken()
    const options = this[operation](tokenData.access_token, ...args)
    console.log(options);
    console.log("--------options url end ------------")
    const data = await this.request(options)
    return data
  }
  // 创建菜单和自定义菜单
  createMenu(token, menu, rules) {
    let url = api.menu.create + '?access_token=' + token
    if (rules) {
      url = api.menu.custom + '?access_token=' + token
      menu.matchrule = rules
    }
    return {
      method: 'POST',
      url,
      body: menu
    }
  }
  // 删除菜单
  deleteMenu(token) {
    const url = api.menu.del + '?access_token=' + token
    return {
      url
    }
  }
  // 查询菜单
  queryMenu(token) {
    return {
      url: api.menu.query + '?access_token=' + token
    }
  }
  // 删除个性化菜单
  deleteCustomMenu(token, menuid) {
    return {
      method: 'POST',
      url: api.menu.delCustom + '?access_token=' + token,
      body: {
        menuid
      }
    }
  }
  // 
  trymatchMenu(token, user_id) {
    return {
      method: 'POST',
      url: api.menu.trymatch + "?access_token=" + token,
      body: {
        user_id
      }
    }
  }
  // 获取菜单
  fetchMenu(token) {
    const url = api.menu.fetch + '?access_token=' + token
    return {
      url
    }
  }
}
